import com.android.Version
import org.apache.tools.ant.filters.ReplaceTokens
import org.apache.tools.ant.taskdefs.condition.Os
import groovy.json.JsonSlurper

import javax.inject.Inject
import java.nio.file.Files
import java.nio.file.Paths

/**
 * Finds the path of the installed npm package with the given name using Node's
 * module resolution algorithm, which searches "node_modules" directories up to
 * the file system root. This handles various cases, including:
 *
 *   - Working in the open-source RN repo:
 *       Gradle: /path/to/react-native/ReactAndroid
 *       Node module: /path/to/react-native/node_modules/[package]
 *
 *   - Installing RN as a dependency of an app and searching for hoisted
 *     dependencies:
 *       Gradle: /path/to/app/node_modules/react-native/ReactAndroid
 *       Node module: /path/to/app/node_modules/[package]
 *
 *   - Working in a larger repo (e.g., Facebook) that contains RN:
 *       Gradle: /path/to/repo/path/to/react-native/ReactAndroid
 *       Node module: /path/to/repo/node_modules/[package]
 *
 * The search begins at the given base directory (a File object). The returned
 * path is a string.
 */
static def findNodeModulePath(baseDir, packageName) {
    def basePath = baseDir.toPath().normalize()
    // Node's module resolution algorithm searches up to the root directory,
    // after which the base path will be null
    while (basePath) {
        def candidatePath = Paths.get(basePath.toString(), "node_modules", packageName)
        if (candidatePath.toFile().exists()) {
            return candidatePath.toString()
        }
        basePath = basePath.getParent()
    }
    return null
}

def safeExtGet(prop, fallback) {
    rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}

def safeAppExtGet(prop, fallback) {
    def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
    appProject?.ext?.has(prop) ? appProject.ext.get(prop) : fallback
}

def resolveBuildType() {
    Gradle gradle = getGradle()
    String tskReqStr = gradle.getStartParameter().getTaskRequests()['args'].toString()
    return tskReqStr.contains('Release') ? 'release' : 'debug'
}

def isReanimatedExampleApp() {
    return safeAppExtGet("isReanimatedExampleApp", false)
}

def isNewArchitectureEnabled() {
    // To opt-in for the New Architecture, you can either:
    // - Set `newArchEnabled` to true inside the `gradle.properties` file
    // - Invoke gradle with `-newArchEnabled=true`
    // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true`
    return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
}

def resolveReactNativeDirectory() {
    def reactNativeLocation = safeAppExtGet("REACT_NATIVE_NODE_MODULES_DIR", null)
    if (reactNativeLocation != null) {
        return file(reactNativeLocation)
    }

    if (isReanimatedExampleApp()) {
        return file("$projectDir/../${getPlaygroundAppName()}/node_modules/react-native")
    }

    // monorepo workaround
    // react-native can be hoisted or in project's own node_modules
    def reactNativeFromProjectNodeModules = file("${rootProject.projectDir}/../node_modules/react-native")
    if (reactNativeFromProjectNodeModules.exists()) {
        return reactNativeFromProjectNodeModules
    }
    
    def reactNativeFromNodeModulesWithReanimated = file("${projectDir}/../../react-native")
    if (reactNativeFromNodeModulesWithReanimated.exists()) {
        return reactNativeFromNodeModulesWithReanimated
    }

    throw new GradleException(
        "[Reanimated] Unable to resolve react-native location in node_modules. You should project extension property (in `app/build.gradle`) `REACT_NATIVE_NODE_MODULES_DIR` with path to react-native."
    )
}

def getPlaygroundAppName() { // only for the development
    String playgroundAppName = ""
    try {
        rootProject.getSubprojects().forEach({project ->
            if (project.plugins.hasPlugin("com.android.application")) {
                var projectCatalogAbsolutePath = project.projectDir.toString().replace("/android/app", "")
                var slashPosition = projectCatalogAbsolutePath.lastIndexOf("/")
                playgroundAppName = projectCatalogAbsolutePath.substring(slashPosition + 1)
            }
        })
    } catch (_) {
        throw new GradleException("[Reanimated] Couldn't determine playground app name.")
    }
    return playgroundAppName
}

def getReanimatedVersion() {
    def inputFile = file(projectDir.path + '/../package.json')
    def json = new JsonSlurper().parseText(inputFile.text)
    return json.version
}

def getReanimatedMajorVersion() {
    def (major, minor, patch) = getReanimatedVersion().tokenize('.')
    return major.toInteger()
}

def toPlatformFileString(String path) {
  if (Os.isFamily(Os.FAMILY_WINDOWS)) {
      path = path.replace(File.separatorChar, '/' as char)
  }
  return path
}

def reactNativeRootDir = resolveReactNativeDirectory()

def reactProperties = new Properties()
file("$reactNativeRootDir/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) }

def REACT_NATIVE_VERSION = reactProperties.getProperty("VERSION_NAME")
def REACT_NATIVE_MINOR_VERSION = REACT_NATIVE_VERSION.startsWith("0.0.0-") ? 1000 : REACT_NATIVE_VERSION.split("\\.")[1].toInteger()
def REANIMATED_VERSION = getReanimatedVersion()
def REANIMATED_MAJOR_VERSION = getReanimatedMajorVersion()
def IS_NEW_ARCHITECTURE_ENABLED = isNewArchitectureEnabled()

// for React Native <= 0.70
def BOOST_VERSION = reactProperties.getProperty("BOOST_VERSION")
def DOUBLE_CONVERSION_VERSION = reactProperties.getProperty("DOUBLE_CONVERSION_VERSION")
def FOLLY_VERSION = reactProperties.getProperty("FOLLY_VERSION")
def GLOG_VERSION = reactProperties.getProperty("GLOG_VERSION")
def FBJNI_VERSION = "0.3.0"

// We download various C++ open-source dependencies into downloads.
// We then copy both the downloaded code and our custom makefiles and headers into third-party-ndk.
// After that we build native code from src/main/jni with module path pointing at third-party-ndk.

def customDownloadsDir = System.getenv("REACT_NATIVE_DOWNLOADS_DIR")
def downloadsDir = customDownloadsDir ? new File(customDownloadsDir) : new File("$buildDir/downloads")
def thirdPartyNdkDir = new File("$buildDir/third-party-ndk")

def reactNativeThirdParty = new File("$reactNativeRootDir/ReactAndroid/src/main/jni/third-party")
def reactNativeAndroidDownloadDir = new File("$reactNativeRootDir/ReactAndroid/build/downloads")

def prefabHeadersDir = project.file("$buildDir/prefab-headers/reanimated")

def JS_RUNTIME = {
    // Override JS runtime with environment variable
    if (System.getenv("JS_RUNTIME")) {
        return System.getenv("JS_RUNTIME")
    }

    // Enable V8 runtime if react-native-v8 is installed
    def v8Project = rootProject.getSubprojects().find { project -> project.name == "react-native-v8" }
    if (v8Project != null) {
        return "v8"
    }

    // Check if Hermes is enabled in app setup
    def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
    if ((REACT_NATIVE_MINOR_VERSION >= 71 && appProject?.hermesEnabled?.toBoolean()) || appProject?.ext?.react?.enableHermes?.toBoolean()) {
        return "hermes"
    }

    // Use JavaScriptCore (JSC) by default
    return "jsc"
}.call()

def jsRuntimeDir = {
    if (JS_RUNTIME == "hermes") {
        if (REACT_NATIVE_MINOR_VERSION >= 69) {
            return Paths.get(reactNativeRootDir.path, "sdks", "hermes")
        } else {
            return Paths.get(reactNativeRootDir.path, "..", "hermes-engine")
        }
    } else if (JS_RUNTIME == "v8") {
        return findProject(":react-native-v8").getProjectDir().getParent()
    } else {
        return Paths.get(reactNativeRootDir.path, "ReactCommon", "jsi")
    }
}.call()

def reactNativeArchitectures() {
    def value = project.getProperties().get("reactNativeArchitectures")
    return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}

buildscript {
    repositories {
        google()
        mavenCentral()
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
    dependencies {
        classpath "com.android.tools.build:gradle:7.3.1"
        classpath "de.undercouch:gradle-download-task:5.0.1"
        classpath "com.diffplug.spotless:spotless-plugin-gradle:6.11.0"
    }
}

if (project == rootProject) {
    apply from: "spotless.gradle"
}

apply plugin: "com.android.library"
apply plugin: "maven-publish"
apply plugin: "de.undercouch.download"

android {
    compileSdkVersion safeExtGet("compileSdkVersion", 30)

    def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
    if (agpVersion.tokenize('.')[0].toInteger() >= 7) {
        namespace "com.swmansion.reanimated"
    }

    if (rootProject.hasProperty("ndkPath")) {
        ndkPath rootProject.ext.ndkPath
    }
    if (rootProject.hasProperty("ndkVersion")) {
        ndkVersion rootProject.ext.ndkVersion
    }

    buildFeatures {
        if (REACT_NATIVE_MINOR_VERSION > 68) {
            prefab true
            prefabPublishing true
            buildConfig true
        }
    }

    prefab {
        reanimated {
            headers prefabHeadersDir.absolutePath
        }
    }

    defaultConfig {
        minSdkVersion safeExtGet("minSdkVersion", 16)
        targetSdkVersion safeExtGet("targetSdkVersion", 30)
        versionCode 1
        versionName "1.0"
        buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", IS_NEW_ARCHITECTURE_ENABLED.toString())
        buildConfigField("String", "REANIMATED_VERSION_JAVA", "\"${REANIMATED_VERSION}\"")
        externalNativeBuild {
            cmake {
                arguments "-DANDROID_STL=c++_shared",
                        "-DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION}",
                        "-DANDROID_TOOLCHAIN=clang",
                        REACT_NATIVE_MINOR_VERSION < 71 ? "-DBOOST_VERSION=${BOOST_VERSION}" : "-DBOOST_VERSION=",
                        "-DREACT_NATIVE_DIR=${toPlatformFileString(reactNativeRootDir.path)}",
                        "-DJS_RUNTIME=${JS_RUNTIME}",
                        "-DJS_RUNTIME_DIR=${jsRuntimeDir}",
                        "-DIS_NEW_ARCHITECTURE_ENABLED=${IS_NEW_ARCHITECTURE_ENABLED}",
                        "-DIS_REANIMATED_EXAMPLE_APP=${isReanimatedExampleApp()}",
                        "-DREANIMATED_VERSION=${REANIMATED_VERSION}"
                abiFilters (*reactNativeArchitectures())
            }
        }

        buildConfigField("boolean", "IS_INTERNAL_BUILD", "false")
        buildConfigField("int", "EXOPACKAGE_FLAGS", "0")
        buildConfigField("int", "REACT_NATIVE_MINOR_VERSION", REACT_NATIVE_MINOR_VERSION.toString())
        
        consumerProguardFiles 'proguard-rules.pro'
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
    buildTypes {
        debug {
            externalNativeBuild {
                cmake {
                    if (JS_RUNTIME == "hermes") {
                        arguments "-DHERMES_ENABLE_DEBUGGER=1"
                    } else {
                        arguments "-DHERMES_ENABLE_DEBUGGER=0"
                    }
                }
            }
        }
        release {
            externalNativeBuild {
                cmake {
                    arguments "-DHERMES_ENABLE_DEBUGGER=0"
                }
            }
        }
    }
    lintOptions {
        abortOnError false
    }
    packagingOptions {
        doNotStrip resolveBuildType() == 'debug' ? "**/**/*.so" : ''
        excludes = [
                "META-INF",
                "META-INF/**",
                "**/libc++_shared.so",
                "**/libfbjni.so",
                "**/libjsi.so",
                "**/libfolly_json.so",
                "**/libfolly_runtime.so",
                "**/libglog.so",
                "**/libhermes.so",
                "**/libhermes-executor-debug.so",
                "**/libhermes_executor.so",
                "**/libreactnativejni.so",
                "**/libturbomodulejsijni.so",
                "**/libreact_nativemodule_core.so",
                "**/libjscexecutor.so",
                "**/libv8executor.so",
        ]
    }
    tasks.withType(JavaCompile) {
        compileTask ->
            compileTask.dependsOn(packageNdkLibs)
    }
    configurations {
        extractHeaders
        extractSO
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    packagingOptions {
        // For some reason gradle only complains about the duplicated version of librrc_root and libreact_render libraries
        // while there are more libraries copied in intermediates folder of the lib build directory, we exclude
        // only the ones that make the build fail (ideally we should only include libreanimated but we
        // are only allowed to specify exlude patterns)
        exclude "**/libreact_render*.so"
        exclude "**/librrc_root.so"
    }
    sourceSets.main {
        java {
            if (IS_NEW_ARCHITECTURE_ENABLED) {
                srcDirs += "src/fabric/java"
            } else {
                srcDirs += "src/paper/java"
            }

            // messageQueueThread
            if (REANIMATED_MAJOR_VERSION > 2) {
                if (REACT_NATIVE_MINOR_VERSION <= 67) {
                    srcDirs += "src/reactNativeVersionPatch/messageQueueThread/67"
                } else if (REACT_NATIVE_MINOR_VERSION <= 72) {
                    srcDirs += "src/reactNativeVersionPatch/messageQueueThread/72"
                } else {
                    srcDirs += "src/reactNativeVersionPatch/messageQueueThread/latest"
                }
            }

            // ReanimatedUIManager & ReanimatedUIImplementation 
            if (REACT_NATIVE_MINOR_VERSION <= 70) {
                srcDirs += "src/reactNativeVersionPatch/ReanimatedUIManager/70"
            } else {
                srcDirs += "src/reactNativeVersionPatch/ReanimatedUIManager/latest"
            }

            // ReactFeatureFlags 
            if (IS_NEW_ARCHITECTURE_ENABLED) {
                if (REACT_NATIVE_MINOR_VERSION <= 72) {
                    srcDirs += "src/reactNativeVersionPatch/ReactFeatureFlagsWrapper/72"
                } else {
                    srcDirs += "src/reactNativeVersionPatch/ReactFeatureFlagsWrapper/latest"
                }
            }
        }
    }
}

def assertLatestReactNativeWithNewArchitecture = task assertLatestReactNativeWithNewArchitectureTask {
    onlyIf { IS_NEW_ARCHITECTURE_ENABLED && REANIMATED_MAJOR_VERSION == 3 && REACT_NATIVE_MINOR_VERSION < 72 }
    doFirst {
        // If you change the minimal React Native version remember to update Compatibility Table in docs
        throw new GradleException(
            "[Reanimated] Outdated version of React Native for New Architecture. Reanimated " + REANIMATED_VERSION + " supports the New Architecture on React Native 0.72.0+. See https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#outdated-version-of-react-native-for-new-architecture for more information."
        )
    }
}

def assertMinimalReactNativeVersion = task assertMinimalReactNativeVersionTask {
    onlyIf { REACT_NATIVE_MINOR_VERSION < 66 }
    doFirst {
        // If you change the minimal React Native version remember to update Compatibility Table in docs
        throw new GradleException("[Reanimated] Unsupported React Native version. Please use 0.66 or newer.")
    }
}

task prepareHeadersForPrefab(type: Copy) {
    from("$projectDir/src/main/cpp")
    from("$projectDir/../Common/cpp/AnimatedSensor")
    from("$projectDir/../Common/cpp/Fabric")
    from("$projectDir/../Common/cpp/LayoutAnimations")
    from("$projectDir/../Common/cpp/NativeModules")
    from("$projectDir/../Common/cpp/ReanimatedRuntime")
    from("$projectDir/../Common/cpp/Registries")
    from("$projectDir/../Common/cpp/SharedItems")
    from("$projectDir/../Common/cpp/Tools")
    include("*.h")
    into(prefabHeadersDir)
}

tasks.preBuild {
    dependsOn assertLatestReactNativeWithNewArchitecture, assertMinimalReactNativeVersion
}

task cleanCmakeCache() {
    tasks.getByName("clean").dependsOn(cleanCmakeCache)
    doFirst {
        delete "${projectDir}/.cxx"
    }
}

task printVersions {
    println "Android gradle plugin: ${Version.ANDROID_GRADLE_PLUGIN_VERSION}"
    println "Gradle: ${project.gradle.gradleVersion}"
}

task createNativeDepsDirectories() {
    downloadsDir.mkdirs()
    thirdPartyNdkDir.mkdirs()
    prefabHeadersDir.mkdirs()
}

def resolveTaskFactory(String taskName, String artifactLocalName, File reactNativeAndroidDownloadDir, File reanimatedDownloadDir) {
    return tasks.create(name: taskName, dependsOn: createNativeDepsDirectories, type: Copy) {
        from reactNativeAndroidDownloadDir
        include artifactLocalName
        into reanimatedDownloadDir

        onlyIf {
            // First we check whether the file is already in our download directory
            if (file("$reanimatedDownloadDir/$artifactLocalName").isFile()) {
                return false
            }

            // If it is not the case we check whether it was downloaded by ReactAndroid project
            if (file("$reactNativeAndroidDownloadDir/$artifactLocalName").isFile()) {
                return true
            }

            return false
        }
    }
}

/*
Reanimated includes "hermes/hermes.h" header file in `NativeProxy.cpp`.
Previously, we used header files from `hermes-engine` package in `node_modules`.
In React Native 0.69 and 0.70, Hermes is no longer distributed as package on NPM.
On the new architecture, Hermes is downloaded from GitHub and then compiled from sources.
However, on the old architecture, we need to download Hermes header files on our own
as well as unzip Hermes AAR in order to obtain `libhermes.so` shared library.
For more details, see https://reactnative.dev/architecture/bundled-hermes
or https://github.com/reactwg/react-native-new-architecture/discussions/4
*/
if (REACT_NATIVE_MINOR_VERSION in [69, 70] && !IS_NEW_ARCHITECTURE_ENABLED) {
    // copied from `react-native/ReactAndroid/hermes-engine/build.gradle`

    def downloadDir = customDownloadsDir ? new File(customDownloadsDir) : new File(reactNativeRootDir, "sdks/download")

    // By default we are going to download and unzip hermes inside the /sdks/hermes folder
    // but you can provide an override for where the hermes source code is located.
    def hermesDir = System.getenv("REACT_NATIVE_OVERRIDE_HERMES_DIR") ?: new File(reactNativeRootDir, "sdks/hermes")

    def hermesVersion = "main"
    def hermesVersionFile = new File(reactNativeRootDir, "sdks/.hermesversion")
    if (hermesVersionFile.exists()) {
        hermesVersion = hermesVersionFile.text
    }

    task downloadHermes(type: Download) {
        src("https://github.com/facebook/hermes/tarball/${hermesVersion}")
        onlyIfNewer(true)
        overwrite(false)
        dest(new File(downloadDir, "hermes.tar.gz"))
    }

    task unzipHermes(dependsOn: downloadHermes, type: Copy) {
        from(tarTree(downloadHermes.dest)) {
            eachFile { file ->
                // We flatten the unzip as the tarball contains a `facebook-hermes-<SHA>`
                // folder at the top level.
                if (file.relativePath.segments.size() > 1) {
                    file.relativePath = new RelativePath(!file.isDirectory(), file.relativePath.segments.drop(1))
                }
            }
        }
        into(hermesDir)
    }
}

if (REACT_NATIVE_MINOR_VERSION < 71) {
    // You need to have following folders in this directory:
    //   - boost_1_63_0
    //   - double-conversion-1.1.6
    //   - folly-deprecate-dynamic-initializer
    //   - glog-0.3.5
    def dependenciesPath = System.getenv("REACT_NATIVE_DEPENDENCIES")

    // The Boost library is a very large download (>100MB).
    // If Boost is already present on your system, define the REACT_NATIVE_BOOST_PATH env variable
    // and the build will use that.
    def boostPath = dependenciesPath ?: System.getenv("REACT_NATIVE_BOOST_PATH")

    def follyReplaceContent = '''
    ssize_t r;
    do {
        r = open(name, flags, mode);
    } while (r == -1 && errno == EINTR);
    return r;
    '''

    Task resolveBoost = resolveTaskFactory("resolveBoost", "boost_${BOOST_VERSION}.tar.gz", reactNativeAndroidDownloadDir, downloadsDir)
    Task resolveDoubleConversion = resolveTaskFactory(
        "resolveDoubleConversion",
        "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz",
        reactNativeAndroidDownloadDir,
        downloadsDir
    )
    Task resolveFolly = resolveTaskFactory("resolveFolly", "folly-${FOLLY_VERSION}.tar.gz", reactNativeAndroidDownloadDir, downloadsDir)
    Task resolveGlog = resolveTaskFactory("resolveGlog", "glog-${GLOG_VERSION}.tar.gz", reactNativeAndroidDownloadDir, downloadsDir)

    if (IS_NEW_ARCHITECTURE_ENABLED) {
        def reactNativeAndroidProject = findProject(":ReactAndroid")
        if (reactNativeAndroidProject != null) {
            reactNativeAndroidProject.afterEvaluate {
                def resolveTasks = [resolveBoost, resolveGlog, resolveDoubleConversion, resolveFolly]
                resolveTasks.forEach({ task ->
                    String reactAndroidDownloadTaskName = "download" + task.name.replace("resolve", "")
                    def reactAndroidDownloadTask = reactNativeAndroidProject.getTasks().findByName(reactAndroidDownloadTaskName)
                    if (reactAndroidDownloadTask != null) {
                        task.dependsOn(reactAndroidDownloadTask)
                    } else {
                        logger.warn("[Reanimated] Failed to find task named `$reactAndroidDownloadTaskName` in `:ReactAndroid` project." +
                                " Explicit dependency between it and $task.name task can not be set.")
                    }
                })
            }
        } else {
            throw new GradleException("[Reanimated] Failed to find `:ReactAndroid` project. Explicit dependency between download tasks can not be set.")
        }
    }

    task downloadBoost(dependsOn: resolveBoost, type: Download) {
        def transformedVersion = BOOST_VERSION.replace("_", ".")
        def artifactLocalName = "boost_${BOOST_VERSION}.tar.gz"
        def srcUrl = "https://boostorg.jfrog.io/artifactory/main/release/${transformedVersion}/source/${artifactLocalName}"
        if (REACT_NATIVE_MINOR_VERSION < 69) {
            srcUrl = "https://github.com/react-native-community/boost-for-react-native/releases/download/v${transformedVersion}-0/${artifactLocalName}"
        }
        src(srcUrl)
        onlyIfNewer(true)
        overwrite(false)
        dest(new File(downloadsDir, artifactLocalName))
    }

    task prepareBoost(dependsOn: boostPath ? [] : [downloadBoost], type: Copy) {
        from(boostPath ?: tarTree(resources.gzip(downloadBoost.dest)))
        from("$reactNativeThirdParty/boost/Android.mk")
        include("Android.mk", "boost_${BOOST_VERSION}/boost/**/*.hpp", "boost/boost/**/*.hpp")
        includeEmptyDirs = false
        into("$thirdPartyNdkDir/boost")
        doLast {
            file("$thirdPartyNdkDir/boost/boost").renameTo("$thirdPartyNdkDir/boost/boost_${BOOST_VERSION}")
        }
    }

    task downloadDoubleConversion(dependsOn: resolveDoubleConversion, type: Download) {
        src("https://github.com/google/double-conversion/archive/v${DOUBLE_CONVERSION_VERSION}.tar.gz")
        onlyIfNewer(true)
        overwrite(false)
        dest(new File(downloadsDir, "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz"))
    }

    task prepareDoubleConversion(dependsOn: dependenciesPath ? [] : [downloadDoubleConversion], type: Copy) {
        from(dependenciesPath ?: tarTree(downloadDoubleConversion.dest))
        from("$reactNativeThirdParty/double-conversion/Android.mk")
        include("double-conversion-${DOUBLE_CONVERSION_VERSION}/src/**/*", "Android.mk")
        filesMatching("*/src/**/*", { fname -> fname.path = "double-conversion/${fname.name}" })
        includeEmptyDirs = false
        into("$thirdPartyNdkDir/double-conversion")
    }

    task downloadFolly(dependsOn: resolveFolly, type: Download) {
        src("https://github.com/facebook/folly/archive/v${FOLLY_VERSION}.tar.gz")
        onlyIfNewer(true)
        overwrite(false)
        dest(new File(downloadsDir, "folly-${FOLLY_VERSION}.tar.gz"))
    }

    task prepareFolly(dependsOn: dependenciesPath ? [] : [downloadFolly], type: Copy) {
        from(dependenciesPath ?: tarTree(downloadFolly.dest))
        from("$reactNativeThirdParty/folly/Android.mk")
        include("folly-${FOLLY_VERSION}/folly/**/*", "Android.mk")
        eachFile { fname -> fname.path = (fname.path - "folly-${FOLLY_VERSION}/") }
        // Fixes problem with Folly failing to build on certain systems. See
        // https://github.com/software-mansion/react-native-reanimated/issues/1024
        filter { line -> line.replaceAll("return int\\(wrapNoInt\\(open, name, flags, mode\\)\\);", follyReplaceContent) }
        includeEmptyDirs = false
        into("$thirdPartyNdkDir/folly")
    }

    task downloadGlog(dependsOn: resolveGlog, type: Download) {
        src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz")
        onlyIfNewer(true)
        overwrite(false)
        dest(new File(downloadsDir, "glog-${GLOG_VERSION}.tar.gz"))
    }

    // Prepare glog sources to be compiled, this task will perform steps that normally should've been
    // executed by automake. This way we can avoid dependencies on make/automake
    task prepareGlog(dependsOn: dependenciesPath ? [] : [downloadGlog], type: Copy) {
        duplicatesStrategy = "include"
        from(dependenciesPath ?: tarTree(downloadGlog.dest))
        from("$reactNativeThirdParty/glog/")
        include("glog-${GLOG_VERSION}/src/**/*", "Android.mk", "config.h")
        includeEmptyDirs = false
        filesMatching("**/*.h.in") {
            filter(ReplaceTokens, tokens: [
                    ac_cv_have_unistd_h           : "1",
                    ac_cv_have_stdint_h           : "1",
                    ac_cv_have_systypes_h         : "1",
                    ac_cv_have_inttypes_h         : "1",
                    ac_cv_have_libgflags          : "0",
                    ac_google_start_namespace     : "namespace google {",
                    ac_cv_have_uint16_t           : "1",
                    ac_cv_have_u_int16_t          : "1",
                    ac_cv_have___uint16           : "0",
                    ac_google_end_namespace       : "}",
                    ac_cv_have___builtin_expect   : "1",
                    ac_google_namespace           : "google",
                    ac_cv___attribute___noinline  : "__attribute__ ((noinline))",
                    ac_cv___attribute___noreturn  : "__attribute__ ((noreturn))",
                    ac_cv___attribute___printf_4_5: "__attribute__((__format__ (__printf__, 4, 5)))"
            ])
            it.path = (it.name - ".in")
        }
        into("$thirdPartyNdkDir/glog")

        doLast {
            copy {
                from(fileTree(dir: "$thirdPartyNdkDir/glog", includes: ["stl_logging.h", "logging.h", "raw_logging.h", "vlog_is_on.h", "**/src/glog/log_severity.h"]).files)
                includeEmptyDirs = false
                into("$thirdPartyNdkDir/glog/exported/glog")
            }
        }
    }

    task prepareHermes {
        if (REACT_NATIVE_MINOR_VERSION >= 69) {
            if (!IS_NEW_ARCHITECTURE_ENABLED) {
                dependsOn(unzipHermes)
            }

            doLast {
                // e.g. hermes-engine-0.70.0-rc.1-debug.aar
                def hermesAAR = file(
                    "$reactNativeRootDir/android/com/facebook/react/hermes-engine/" +
                    "${REACT_NATIVE_VERSION}/hermes-engine-${REACT_NATIVE_VERSION}-" +
                    "${resolveBuildType()}.aar"
                )
                if (!hermesAAR.exists()) {
                    throw new GradleException("[Reanimated] Could not find hermes-engine AAR.", null)
                }

                def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" })

                copy {
                    from soFiles
                    from "$reactNativeRootDir/ReactAndroid/src/main/jni/first-party/hermes/Android.mk"
                    into "$thirdPartyNdkDir/hermes"
                }
            }
        } else {
            doLast {
                def hermesPackagePath = findNodeModulePath(projectDir, "hermes-engine")
                if (!hermesPackagePath) {
                    throw new GradleException("[Reanimated] Could not find the hermes-engine npm package.", null)
                }

                def hermesAAR = file("$hermesPackagePath/android/hermes-${resolveBuildType()}.aar") // e.g. hermes-debug.aar
                if (!hermesAAR.exists()) {
                    throw new GradleException("[Reanimated] The hermes-engine npm package is missing \"android/hermes-${resolveBuildType()}.aar\".", null)
                }

                def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" })

                copy {
                    from soFiles
                    from "$reactNativeRootDir/ReactAndroid/src/main/jni/first-party/hermes/Android.mk"
                    into "$thirdPartyNdkDir/hermes"
                }
            }
        }
    }

    task prepareJSC {
        if (REACT_NATIVE_MINOR_VERSION >= 71) {
            // do nothing
        } else {
            doLast {
                def jscPackagePath = findNodeModulePath(projectDir, "jsc-android")
                if (!jscPackagePath) {
                    throw new GradleException("[Reanimated] Could not find the jsc-android npm package.", null)
                }

                def jscDist = file("$jscPackagePath/dist")
                if (!jscDist.exists()) {
                    throw new GradleException("[Reanimated] The jsc-android npm package is missing its \"dist\" directory.", null)
                }

                def jscAAR = fileTree(jscDist).matching({ it.include "**/android-jsc/**/*.aar" }).singleFile
                def soFiles = zipTree(jscAAR).matching({ it.include "**/*.so" })

                def headerFiles = fileTree(jscDist).matching({ it.include "**/include/*.h" })

                copy {
                    from(soFiles)
                    from(headerFiles)
                    from("$reactNativeRootDir/ReactAndroid/src/main/jni/third-party/jsc/Android.mk")

                    filesMatching("**/*.h", { it.path = "JavaScriptCore/${it.name}" })

                    includeEmptyDirs(false)
                    into("$thirdPartyNdkDir/jsc")
                }
            }
        }
    }

    task extractAARHeaders {
        doLast {
            configurations.extractHeaders.files.each {
                def file = it.absoluteFile
                def packageName = file.name.tokenize('-')[0]
                copy {
                    from zipTree(file)
                    into "$reactNativeRootDir/ReactAndroid/src/main/jni/first-party/$packageName/headers"
                    include "**/*.h"
                }
            }
        }
    }

    task extractSOFiles {
        doLast {
            configurations.extractSO.files.each {
                def file = it.absoluteFile
                def packageName = file.name.tokenize('-')[0]
                copy {
                    from zipTree(file)
                    into "$reactNativeRootDir/ReactAndroid/src/main/jni/first-party/$packageName/"
                    include "jni/**/*.so"
                }
            }
        }
    }

    task unpackReactNativeAAR {
        def buildType = resolveBuildType()
        def rnAarMatcher = "**/react-native/**/*${buildType}.aar"
        if (REACT_NATIVE_MINOR_VERSION < 69) {
            rnAarMatcher = "**/**/*.aar"
        }
        def rnAAR = fileTree("$reactNativeRootDir/android").matching({ it.include rnAarMatcher }).singleFile
        def file = rnAAR.absoluteFile
        def packageName = file.name.tokenize('-')[0]
        copy {
            from zipTree(file)
            into "$reactNativeRootDir/ReactAndroid/src/main/jni/first-party/$packageName/"
            include "jni/**/*.so"
        }
    }

    task downloadNdkBuildDependencies {
        if (!boostPath) {
            dependsOn(downloadBoost)
        }
        dependsOn(downloadDoubleConversion)
        dependsOn(downloadFolly)
        dependsOn(downloadGlog)
    }

    task prepareThirdPartyNdkHeaders(dependsOn:[
        downloadNdkBuildDependencies,
        prepareBoost,
        prepareDoubleConversion,
        prepareFolly,
        prepareGlog,
        unpackReactNativeAAR]
    ) {}
}

task packageNdkLibs(type: Copy) {
    from("$buildDir/reanimated-ndk/all")
    include("**/libreanimated.so")
    into("$projectDir/src/main/jniLibs")
}

repositories {
    mavenCentral()
    mavenLocal()
    maven {
        // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
        url "$reactNativeRootDir/android"
    }
    maven {
        // Android JSC is installed from npm
        url "$reactNativeRootDir/../jsc-android/dist"
    }
    google()
}

dependencies {
    implementation "com.facebook.yoga:proguard-annotations:1.19.0"
    implementation "androidx.transition:transition:1.1.0"
    implementation "androidx.core:core:1.6.0"

    if (REACT_NATIVE_MINOR_VERSION >= 71) {
        implementation "com.facebook.react:react-android" // version substituted by RNGP
        if (JS_RUNTIME == "hermes") {
            implementation "com.facebook.react:hermes-android" // version substituted by RNGP
        }
    } else {
        // noinspection GradleDynamicVersion
        implementation "com.facebook.react:react-native:+" // From node_modules
        implementation "com.facebook.fbjni:fbjni-java-only:" + FBJNI_VERSION

        extractHeaders("com.facebook.fbjni:fbjni:" + FBJNI_VERSION + ":headers")
        extractSO("com.facebook.fbjni:fbjni:" + FBJNI_VERSION)

        def jscAAR = fileTree("$reactNativeRootDir/../jsc-android/dist/org/webkit/android-jsc").matching({ it.include "**/**/*.aar" }).singleFile
        extractSO(files(jscAAR))
    }
}

def nativeBuildDependsOn(dependsOnTask) {
    def buildTasks = tasks.findAll({ task -> (
        !task.name.contains("Clean")
        && (task.name.contains("externalNative")
            || task.name.contains("CMake")
            || task.name.contains("generateJsonModel")
        )
    ) })
    buildTasks.forEach { task -> task.dependsOn(dependsOnTask) }
}

afterEvaluate {
    if (REACT_NATIVE_MINOR_VERSION < 71) {
        extractAARHeaders.dependsOn(prepareThirdPartyNdkHeaders)
        extractSOFiles.dependsOn(prepareThirdPartyNdkHeaders)

        nativeBuildDependsOn(prepareThirdPartyNdkHeaders)
        nativeBuildDependsOn(extractAARHeaders)
        nativeBuildDependsOn(extractSOFiles)
    }

    preBuild.dependsOn(prepareHeadersForPrefab)

    tasks.forEach({ task ->
        if (task.name.contains("JniLibFolders")) {
            task.dependsOn(packageNdkLibs)
        }
    })

    if (JS_RUNTIME == "hermes") {
        if (REACT_NATIVE_MINOR_VERSION < 71) {
            extractAARHeaders.dependsOn(prepareHermes)
            extractSOFiles.dependsOn(prepareHermes)
        }
    } else if (JS_RUNTIME == "v8") {
        def buildTasks = tasks.findAll({ task ->
            !task.name.contains("Clean") && (task.name.contains("externalNative") || task.name.contains("CMake") || task.name.startsWith("generateJsonModel")) })
        buildTasks.forEach { task ->
            def buildType = task.name.endsWith('Debug') ? 'Debug' : 'Release'
            task.dependsOn(":react-native-v8:copy${buildType}JniLibsProjectOnly")
        }
    } else if (JS_RUNTIME == "jsc") {
        if (REACT_NATIVE_MINOR_VERSION < 71) {
            extractAARHeaders.dependsOn(prepareJSC)
            extractSOFiles.dependsOn(prepareJSC)
        }
    } else {
      throw GradleScriptException("[Reanimated] Unknown JS runtime ${JS_RUNTIME}.")
    }
}
